
/* Rendering shadows using projective shadows. */

#include <GL/glut.h>
#include <stdlib.h>
#include "imageloader.h"//the loader of bmp images, found on inter

//Ids of textures
GLuint _texture1Id, _texture2Id, _metalTextureId, _currentTextureId; 


//Makes the image into a texture, and returns the id of the texture
GLuint loadTexture(Image* image) {
	GLuint textureId;
	glGenTextures(1, &textureId); //Make room for our texture
	glBindTexture(GL_TEXTURE_2D, textureId); //Tell OpenGL which texture to edit
	//Map the image to the texture
	glTexImage2D(GL_TEXTURE_2D,                //Always GL_TEXTURE_2D
				 0,                            //0 for now
				 GL_RGB,                       //Format OpenGL uses for image
				 image->width, image->height,  //Width and height
				 0,                            //The border of the image
				 GL_RGB, //GL_RGB, because pixels are stored in RGB format
				 GL_UNSIGNED_BYTE, //GL_UNSIGNED_BYTE, because pixels are stored
				                   //as unsigned numbers
				 image->pixels);               //The actual pixel data
	return textureId; //Returns the id of the texture
}

enum {
  X, Y, Z, W
};
enum {
  A, B, C, D
};

/* create a matrix that will project the desired shadow */
void shadowmatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4])
{
	GLfloat dot;
	
	/* find dot product between light position vector and ground plane normal */
	dot = groundplane[X] * lightpos[X] +
	groundplane[Y] * lightpos[Y] +
	groundplane[Z] * lightpos[Z] +
	groundplane[W] * lightpos[W];
	
	shadowMat[0][0] = dot - lightpos[X] * groundplane[X];
	shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];
	shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];
	shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];
	
	shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];
	shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];
	shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];
	shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];
	
	shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];
	shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];
	shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];
	shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];
	
	shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];
	shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];
	shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];
	shadowMat[3][3] = dot - lightpos[W] * groundplane[W];

}

/* find the plane equation given 3 points */
void findplane(GLfloat plane[4], GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])
{
	GLfloat vec0[3], vec1[3];
	
	/* need 2 vectors to find cross product */
	vec0[X] = v1[X] - v0[X];
	vec0[Y] = v1[Y] - v0[Y];
	vec0[Z] = v1[Z] - v0[Z];
	
	vec1[X] = v2[X] - v0[X];
	vec1[Y] = v2[Y] - v0[Y];
	vec1[Z] = v2[Z] - v0[Z];
	
	/* find cross product to get A, B, and C of plane equation */
	plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];
	plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);
	plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];
	
	plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);
}


void DrawSphere()
{
	glPushMatrix(); 
	
	//load parameters
	GLUquadric* params = gluNewQuadric(); 
	//Activate texturing
	gluQuadricTexture(params,GL_TRUE); 
	
	//Hide limit of texturing
	glTranslatef(60.f, -50.f, -360.f);
	glRotated(180, 0, 1, 0);
	glRotated(-110, 1, 0, 0);
	//gluSphere(GLUquadric* params,radius,slices,stacks);;
	gluSphere(params, 20.f, 20, 20); 
	
	//delete quadric
	gluDeleteQuadric(params); 
	
	//set at it was previously
	glPopMatrix(); 
}

void DrawCylinder()
{
	glPushMatrix(); 
	
	GLUquadric* params = gluNewQuadric(); 
	gluQuadricTexture(params,GL_TRUE); 
	
	//rotate to place the cone correctly and hide the limit of the texturing
	glTranslatef(-40.f, -40.f, -400.f);
	glRotated(-110, 0, 1, 0);
	glRotated(-90, 1, 0, 0);
	
	//gluCylinder(GLUquadric* params,base,top,height,slices,stacks);
	gluCylinder(params, 20., 0., 60., 20, 20); 
		
	gluDeleteQuadric(params); 
	
	glPopMatrix(); 
}

void DrawCube()
{
	glPushMatrix(); 

	glTranslatef(-20.f, -40.f, -340.f);
	glRotated(-30, 0, 1, 0);
   	//Draw cube
	glBegin(GL_QUADS);
		//front face
		glTexCoord2d(0,1);  glVertex3d(0,0,0);
		glTexCoord2d(0,0);  glVertex3d(40,0,0);
		glTexCoord2d(1,0);  glVertex3d(40,-40,0);
		glTexCoord2d(1,1);  glVertex3d(0,-40,0);
		
		//right face
		glTexCoord2d(0,1);  glVertex3d(40,0,0);
		glTexCoord2d(0,0);  glVertex3d(40,0,-40);
		glTexCoord2d(1,0);  glVertex3d(40,-40,-40);
		glTexCoord2d(1,1);  glVertex3d(40,-40,0);
		
		//up face
		glTexCoord2d(0,1);  glVertex3d(0,0,0);
		glTexCoord2d(0,0);  glVertex3d(40,0,0);
		glTexCoord2d(1,0);  glVertex3d(40,0,-40);
		glTexCoord2d(1,1);  glVertex3d(0,0,-40);
	glEnd();

	glPopMatrix();
}


enum {
  NONE, SHADOW
};

int rendermode = NONE;

void menu(int mode)
{
	rendermode = mode;
	glutPostRedisplay();
}

GLfloat floorshadow[4][4];
GLfloat lightpos[] = {50.f, 50.f, -320.f, 1.f};

void redraw(void)
{
  	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	//material settings
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	// Draw floor 
	glEnable(GL_TEXTURE_2D);

	//Select the texture for objects
	glBindTexture(GL_TEXTURE_2D, _metalTextureId);

	glBegin(GL_QUADS);
	glNormal3f(0.f, 1.f, 0.f);
	glTexCoord2i(0, 0);
	glVertex3f(-100.f, -100.f, -320.f);
	glTexCoord2i(4, 0);
	glVertex3f(100.f, -100.f, -320.f);
	glTexCoord2i(4, 4);
	glVertex3f(100.f, -100.f, -520.f);
	glTexCoord2i(0, 4);
	glVertex3f(-100.f, -100.f, -520.f);
	glEnd();

  	glDisable(GL_TEXTURE_2D);

	if (rendermode == SHADOW) {
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_LIGHTING);
		glColor3f(0.01f, 0.01f, 0.01f);  // shadow color 
		
		//Draw Cone projected on the floor
		glPushMatrix();
		glMultMatrixf((GLfloat *) floorshadow);
		DrawCylinder();
		glPopMatrix();
		
		//Draw sphere projected on the floor
		glPushMatrix();
		glMultMatrixf((GLfloat *) floorshadow);
		DrawSphere();
		glPopMatrix();
		
		//Draw cube projected on the floor
		glPushMatrix();
		glMultMatrixf((GLfloat *) floorshadow);
		DrawCube();
		glPopMatrix();
		
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHTING);
	}

	if (rendermode == SHADOW) {
		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_ALWAYS, 1, 0);
		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	}	

	//Select the texture for objects
	glBindTexture(GL_TEXTURE_2D, _metalTextureId);

	//Draw objects
	glEnable(GL_TEXTURE_2D);
	//Draw Cone
	DrawCylinder();
	//Draw Sphere
  	DrawSphere();
	//Draw Cube
	DrawCube();
	glDisable(GL_TEXTURE_2D);

  	glutSwapBuffers();  
}

void key(unsigned char key, int x, int y)
{
  if (key == '\033')
    exit(0);
}

int main(int argc, char *argv[])
{
	GLfloat plane[4];
	GLfloat v0[3], v1[3], v2[3];
	
	glutInit(&argc, argv);
	glutInitWindowSize(512, 512);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL | GLUT_DOUBLE);
	glutCreateWindow("projection shadows");
	glutDisplayFunc(redraw);
	glutKeyboardFunc(key);
	
	glutCreateMenu(menu);
	glutAddMenuEntry("No Shadows", NONE);
	glutAddMenuEntry("Shadows", SHADOW);
	glutAttachMenu(GLUT_RIGHT_BUTTON);
	
	/* draw a perspective scene */
	glMatrixMode(GL_PROJECTION);
	glFrustum(-100., 100., -100., 100., 320., 640.);
	glMatrixMode(GL_MODELVIEW);
	
	/* turn on features */
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	
	/* make shadow matricies */
	
	/* 3 points on floor */
	v0[X] = -100.f;
	v0[Y] = -100.f;
	v0[Z] = -320.f;
	v1[X] = 100.f;
	v1[Y] = -100.f;
	v1[Z] = -320.f;
	v2[X] = 100.f;
	v2[Y] = -100.f;
	v2[Z] = -520.f;
	
	findplane(plane, v0, v1, v2);
	shadowmatrix(floorshadow, plane, lightpos);
	
	/* place light 0 in the right place */
	glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
	
	/* remove back faces to speed things up */
	glCullFace(GL_BACK);

	/*load textures in memory
          use a loadBMP function found on the internet
	*/
	Image* image = loadBMP("metal.bmp");
	_metalTextureId = loadTexture(image);
	delete image;

	Image* image2 = loadBMP("texture2.bmp");
	_texture1Id = loadTexture(image2);
	delete image2;

	Image* image3 = loadBMP("texture1.bmp");
	_texture2Id = loadTexture(image3);
	delete image3;

	_currentTextureId = _texture2Id;

	glutMainLoop();
	return 0;             
}
